package com.fang.mmy.user.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fang.mmy.entity.ResponseResult;
import com.fang.mmy.entity.User;
import com.fang.mmy.user.feign.ItemFeign;
import com.fang.mmy.user.handler.UserSentinelResourceHandler;
import com.fang.mmy.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName UserController
 * @Author kebukeyi
 * @Date 2022/8/7 9:45
 * @Description
 * @Version 1.0.0
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private ItemFeign itemFeign;

    /**
     * 流控规则 ： QPS、线程数量
     * <p>
     * 资源名： 唯一名称，默认请求路径，表示对该资源进行流控
     * 针对来源： Sentinel可以针对调用者进行限流，填写微服务名，默认default（不区分来源）
     * 阈值类型/单击阈值：
     * - QPS：（每秒钟的请求数量）：当调用该api的QPS达到阈值时，进行限流
     * - 线程数：当调用该线程数达到阈值的时候，进行限流
     * 是否集群：不需要集群
     * 流控模式：
     * - 直接： api达到限流条件时，直接限流
     * - 关联： 当关联的资源达到阈值时，就限流自己
     * - 链路： 只记录指定链路上的流量（指定资源从入口资源进来的流量，如果达到阈值，就进行限流）【api级别的针对来源】
     * 流控效果：
     * - 快速失败： 直接失败，抛异常
     * - Warm Up： 根据codeFactor（冷加载因子，默认3）的值，从阈值/codeFctor，经过预热时长，才达到设置的QPS阈值
     * - 排队等待： 匀速排队，让请求以匀速的速度通过，阈值类型必须设置为QPS，否则无效
     */

    /**
     * @return
     * @SentinelResource 的属性
     * value：作用指定资源名称，必填
     * entryType：entry类型，标记流量的方向，指明是出口流量，还是入口流量；取值 IN/OUT ,默认是OUT。非必填
     * blockHandler：处理BlockException的函数名称，函数要求为
     * - 必须是public
     * - 返回类型与原方法一致
     * - 参数类型需要和原方法相匹配，并在最后加上BlockException类型的参数
     * - 默认需和原方法在同一个类中，如果希望使用其他类的函数，可配置blockHandlerClass,并指定blockHandlerClass里面的方法
     * blockHandlerClass：存放blockHandler的类。对应的处理函数必须static修饰，否则无法解析。函数要求为：
     * - 必须是public
     * - 返回类型与原方法一致
     * - 参数类型需要和原方法相匹配，并在最后加上BlockException类型的参数
     * fallback：用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常（除了execptionsToIgnore 里面排除掉的异常类型）进行处理，函数要求为：
     * - 返回类型与原方法一致
     * - 参数类型需要和原方法相匹配，Sentinel 1.6版本之后，也可在方法最后加上Throwable类型的参数
     * - 默认需和原方法在同一个类中，若希望使用其他类的函数，可配置fallbackClass，并指定fallbackClass里面的方法
     * fallbackClass：存放fallback的类。对应的处理函数必须static修饰，否则无法解析，其他要求：同fallback
     * defaultFallback：用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常（除了 exceptionsToIgnore 里面排除掉的异常类型）进行处理。若同时配置了 fallback 和 defaultFallback，以fallback为准。函数要求：
     * - 返回类型与原方法一致
     * - 方法参数列表为空，或者有一个Throwable类型的参数
     * - 默认需要和原方法在同一个类中，若希望使用其他类的函数，可配置fallbackclass，并指定fallbackClass里面的方法。
     * exceptionsToIgnore：指定排除掉哪些异常。排除的异常不会计入异常统计，也不会进入fallback逻辑，而是原样抛出
     * exceptionsToTrace：需要trace的异常
     */
    @GetMapping("/getUserNo")
    @SentinelResource(value = "getOrderNoResource", blockHandler = "getOrderNoBlockHandler", blockHandlerClass = UserController.class)
    public ResponseResult<User> getUserNo(Long userId) {
        return userService.getUserNo(userId);
    }

    public static String getOrderNoBlockHandler(String userId, BlockException e) {
        String msg = "不好意思，前方拥挤，请您稍后再试";
        return msg;
    }

    // 流控规则之 关联资源
    @GetMapping("/sentinelB")
    public String sentinelB() {
        return "我是关联接口";
    }

    /**
     * 熔断规则
     * 慢调用比例 (SLOW_REQUEST_RATIO)：选择以慢调用比例作为阈值，需要设置允许的慢调用 RT（即最大的响应时间），
     * 请求的响应时间大于该值则统计为慢调用。当单位统计时长（statIntervalMs）内请求数目大于设置的最小请求数目，并且慢调用的比例大于阈值，
     * 则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），
     * 若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断，若大于设置的慢调用 RT 则会再次被熔断
     * <p>
     * 异常比例 (ERROR_RATIO)：当单位统计时长（statIntervalMs）内请求数目大于设置的最小请求数目，并且异常的比例大于阈值，
     * 则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），
     * 若接下来的一个请求成功完成（没有错误）则结束熔断，否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]，代表 0% - 100%
     * <p>
     * 异常数 (ERROR_COUNT)：当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
     * 经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），若接下来的一个请求成功完成（没有错误）则结束熔断，否则会再次被熔断
     * <p>
     * 资源名  sentinelAResource 、熔断策略 慢比例调用 、 最大RT 200ms 、比例阙值 0.1 、熔断时长5s 、最小请求数 5 、统计时长 2000ms
     * 我们对以上添加的熔断规则解释一下：
     * 在 2 秒的统计时间（统计时长）内，对资源接口的请求数大于 5 （最小请求数），请求响应时间（最大 RT）大于200毫秒的请求占比超过比例阈值 0.1 ，
     * 则下一个请求开始触发熔断，熔断时长是 5 秒，也就是 5 秒内，熔断器是打开状态，
     * 这期间的请求都会触发熔断，5秒过后熔断器关闭，此时熔断器会进入探测恢复状态（HALF-OPEN 状态），
     * 若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断，若大于设置的慢调用 RT 则会再次被熔断。
     * RT（平均响应时间）： 当1s内持续进入5个请求，且对应请求的平均响应时间（秒级）均超过阈值，那么在接下来的时间窗口期内，
     * 对该方法的调用都会自动的熔断。注意Sentinel默认统计的RT上限是4900ms，超出此阈值的都会算作4900ms，
     * 若需要更改上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置
     */
    @GetMapping("/sentinelA")
    @SentinelResource(value = "sentinelAResource", fallback = "sentinelAResource", fallbackClass = UserSentinelResourceHandler.class)
    public String sentinelA() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是sentinelA");
        return "我是sentinelA";
    }


    /**
     * 远程 OpenFeign 调用
     */
    @GetMapping("/getItemFeignNo")
    public String getItemFeignNo(String userId) {
        return itemFeign.getItemNo(userId);
    }

    /**
     * 热点规则：热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据，并对其访问进行限制，比如对某个商品id进行限制，或者对某个用户id进行限制
     * 热点规则属性
     * 参数索引：方法中参数的索引第几个参数
     * 单机阈值：每秒达到单机阈值的数量就会触发兜底方法
     *
     * @param userId
     * @param shopId
     * @return
     */
    @GetMapping("/hotspot")
    @SentinelResource(value = "hotspotResource", blockHandler = "hotspotResource", blockHandlerClass = UserSentinelResourceHandler.class)
    public String hotspot(@RequestParam(value = "userId", required = false) String userId, @RequestParam(value = "shopId", required = false) String shopId) {
        System.out.println("我是hotspot");
        return "我是hotspot";
    }


}
